Bloom Filter简介。
布隆过滤器 是用来判断一个元素是否在集合中的数据结构,它使用一系列Hash函数和一个位数组来判断。
首先需要准备一个长度为m的位数组,每一位初始化为0,然后在存元素的时候通过k个Hash函数分别将元素映射到数组的k个位置,并设置为1。之后在判断某一元素是否存在于集合中就只需要判断通过k个Hash函数分别将元素映射到数组的k个位是否都为1,如果不是,那么该元素一定不在集合中,如果是,那么元素有一定概率在集合中,这个概率是多少,取决于m,k和存储在集合中的元素个数n。
上图将x,y,z存入集合,将对应的位设为1,然后查询w是否在集合中,由于通过3个Hash函数计算出来的对应位置有一位不为1,则表示w不在集合中。
概率如何计算:Probability of false positives 这里讲的比较详细了。
其实我们可以发现如果m越长,n越少,k越多那么误判的几率就越低。
k的选择需要考虑m和n的值,可以详看上面的链接,这里直接给出k取$(\frac mn)ln2$为最佳。
Bloom Filter的时间和空间优势还是挺明显的,相比较HashSet等其他方式来判断是否在集合中,空间上由于Bloom Filter并不存储数据本身,而只用了一个位数组,这能极大的节省空间,时间上面需要通过k个Hash函数,可以认为是常数的复杂度。缺点就是会出现误判,也不支持删除操作。
虽然Bloom Filter通过合理的选择m和k能有效降低误判,但是还是免不了出现误判,所以Bloom Filter适合哪些不要求百分百正确的场景,比如垃圾邮件,黑名单等等。
代码如下(Github地址 ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package io.github.ztmark;import com.google.common.collect.ImmutableList;import com.google.common.hash.HashFunction;import com.google.common.hash.Hashing;import java.util.ArrayList;import java.util.BitSet;import java.util.List;import java.util.Random;public class BloomFilter <T > { private final BitSet bitSet; private final int bitSize; private final int expectedNumberOfElements; private final ImmutableList<HashFunction> hashFunctions; private final int k; private int addedNumberOfElements; public BloomFilter (int bitSize, int expectedNumberOfElements) { if (bitSize <= 0 || expectedNumberOfElements <= 0 ) { throw new RuntimeException("wrong parameters bitSize and expectedNumberOfElements should greater than zero." ); } this .bitSize = bitSize; bitSet = new BitSet(bitSize); this .expectedNumberOfElements = expectedNumberOfElements; k = (int ) Math.round(1.0 * bitSize / expectedNumberOfElements * Math.log(2 )); hashFunctions = HashFunctionGenerator.generateKHashFunctions(k); addedNumberOfElements = 0 ; } public void add (T elem) { for (HashFunction function : hashFunctions) { bitSet.set(Math.abs(function.hashInt(elem.hashCode()).asInt() / bitSize)); } addedNumberOfElements++; } public boolean contains (T elem) { for (HashFunction function : hashFunctions) { if (!bitSet.get(Math.abs(function.hashInt(elem.hashCode()).asInt() / bitSize))) { return false ; } } return true ; } public double getFalsePositiveProbability () { return Math.pow((1 - Math.exp(-k * 1.0 * addedNumberOfElements / bitSize)), k); } public int getAddedNumberOfElements () { return addedNumberOfElements; } public int getBitSize () { return bitSize; } public int getExpectedNumberOfElements () { return expectedNumberOfElements; } public int getNumberOfHashFunction () { return k; } private static final class HashFunctionGenerator { public static HashFunction generateAHashFunction () { return Hashing.murmur3_128(new Random(System.currentTimeMillis()).nextInt()); } public static ImmutableList<HashFunction> generateKHashFunctions (int k) { Random random = new Random(System.currentTimeMillis()); List<HashFunction> hashFunctions = new ArrayList<HashFunction>(); for (int i = 0 ; i < k; i++) { hashFunctions.add(Hashing.murmur3_128(random.nextInt())); } return ImmutableList.copyOf(hashFunctions); } } }